Repository navigation
fix(hooks): stop Cursor plugin hooks failing silently on every event - #7
Merged
Merged
Conversation
Installed Cursor plugins were fully inert -- loaded and wired, but no
hook ever executed anything. Three chained defects, each masking the
next:
- plugin-scanner.ts read entry.hooks[] (Claude Code's nested shape),
but a Cursor plugin hooks.json is flat ({ command, matcher?, type? })
-> TypeError on every manifest, swallowed by a catch that exited 0.
- matchesFilter always tested tool_name, while Cursor tests the shell
command line on beforeShellExecution/afterShellExecution, a
synthesized MCP:<tool_name> prefix on
beforeMCPExecution/afterMCPExecution, subagent_type on
subagentStart/subagentStop, and fixed constants on
beforeSubmitPrompt/stop -- the MCP and shell guards could never
match.
- ./scripts/hook.sh was never resolved to an absolute path: the spawn
passes CURSOR_PLUGIN_ROOT via env but no cwd, so the script was
never found and hooks failed open silently.
hooks-loader.ts now logs to stderr before failing open instead of
exiting clean on error.
Rewrote the hooks-harness-exclusive anti-regression gate: it produced
22 assertions with zero on an actual command (it iterated the nested
shape), and its criterion looked for the harness binary directly in
the manifest even though Cursor submission requires relative paths.
New two-layer invariant: manifest delegates to ./scripts/hook.sh, and
that wrapper invokes the harness. Two more defects found by sniper and
fixed in the gate itself: a command regex that let shell injection
through (&&, ;, $(...), backticks, redirections), and a throw during
bun:test collection that silently dropped every check for a plugin.
Verified before commit: bun test 278 pass / 0 fail / 548 expect across
30 files (was 194 pass / 464 expect); bunx tsc --noEmit exit 0; the
targeted gate went from 22 to 106 assertions, with a proven red path
(7 injection payloads blocked, 0 leaks; 63 real manifest commands, 0
false rejects).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
plugin-scanner.tsparsed the nested Claude Code hooks shape instead of Cursor's flat one,matchesFilteronly testedtool_nameinstead of the per-event field Cursor actually sends, and./scripts/hook.shwas never resolved with acwdbefore spawn.hooks-loader.tsnow logs to stderr before failing open instead of exiting clean on error.hooks-harness-exclusiveanti-regression gate (was 22 assertions with zero on an actual command; now 106, two-layer invariant manifest -> wrapper -> harness), including two sniper-found fixes: a command regex that let shell injection through, and a throw duringbun:testcollection that silently dropped a whole plugin's checks.Changes
.cursor-plugin/scripts/hooks-loader.ts.cursor-plugin/scripts/src/interfaces/hooks.ts.cursor-plugin/scripts/src/services/plugin-scanner.ts.cursor-plugin/scripts/src/__tests__/plugin-scanner.test.ts.cursor-plugin/scripts/src/__tests__/plugin-scanner-home-resolution.test.ts.cursor-plugin/scripts/src/__tests__/plugin-scanner-matcher.test.ts(new).cursor-plugin/scripts/src/__tests__/plugin-scanner-script-path.test.ts(new).cursor-plugin/scripts/src/__tests__/hooks-harness-exclusive.test.ts.cursor-plugin/scripts/src/__tests__/hooks-harness-exclusive.helpers.tsCHANGELOG.md(0.1.3)Test plan
bun testfrom.cursor-plugin/scripts: 278 pass / 0 fail / 548 expect across 30 files (was 194 pass / 464 expect)bunx tsc --noEmit: exit 0hooks-harness-exclusive): 22 -> 106 assertionsBreaking changes
None